/*
 * Copyright 2008 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

#include "Platform.h"

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cerrno>

#include "Socket.h"

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <fcntl.h>


void Socket::init() {
#ifdef _WINDOWS
    // version 2.2 supported on Win95OSR2/WinNT4 and up
    WORD winsockVers = MAKEWORD(2, 2);
    WSADATA wsaData;
    int err = WSAStartup(winsockVers, &wsaData);
    if (err) {
        // TODO(jat): report error
        //        Debug::log(Debug::Error) << "WSAStartup(vers=2.2): err=" << err << Debug::flush;
    }
#endif
}

bool Socket::connect(const char* host, int port) {
    //    Debug::log(Debug::Debugging) << "Socket::connect(host=" << host << ",port=" << port << ")"
    //    << Debug::flush;
    if (isConnected()) {
        //        Debug::log(Debug::Error) << "Socket::connect - already connected" << Debug::flush;
        return false;
    }

    SOCKETTYPE fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (fd < 0) {
        //        Debug::log(Debug::Error) << "Socket::connect - can't get socket" << Debug::flush;
        return false;
    }
#ifdef SO_NOSIGPIPE
    // On BSD, we need to suppress the SIGPIPE if the remote end disconnects.
    int option_value = 1;
    if (setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &option_value, sizeof(int))) {
        //        Debug::log(Debug::Error) << "Socket::connect - can't set NOSIGPIPE option" << Debug::flush;
        return false;
    }
#endif

    struct sockaddr_in sockAddr;
    memset(&sockAddr, 0, static_cast<int>(sizeof(sockAddr)));
    // check for numeric IP4 addresses first
    // TODO(jat): handle IPv6 addresses
    unsigned long numericAddr = inet_addr(host);
    if (numericAddr != 0xFFFFFFFF) {
        sockAddr.sin_addr.s_addr = numericAddr;
        sockAddr.sin_family = AF_INET;
    } else {
        struct hostent* hent = gethostbyname(host);
        if (!hent || !hent->h_addr_list[0]) {
            //            Debug::log(Debug::Error) << "Unable to get address for " << host << Debug::flush;
            return false;
        }
        memcpy(&(sockAddr.sin_addr), hent->h_addr_list[0], hent->h_length);
        sockAddr.sin_family = hent->h_addrtype;
    }
    sockAddr.sin_port = htons(port);

    if (::connect(fd, (struct sockaddr*) &sockAddr, sizeof(sockAddr)) < 0) {
#ifdef _WINDOWS
        char buf[256];
        strerror_s(buf, sizeof(buf), errno);
        //        Debug::log(Debug::Error) << "Can't connect to " << host << ":" << port << " -- "
        //        << buf << Debug::flush;
        closesocket(fd);
#else
        //        Debug::log(Debug::Error) << "Can't connect to " << host << ":" << port << " -- "
        //        << strerror(errno) << Debug::flush;
        close(fd);
#endif
        return false;
    }
    sock = fd;
    connected = true;
    readBufPtr = readValid = readBuf;
    writeBufPtr = writeBuf;
#ifdef _WINDOWS
    //    Debug::log(Debug::Spam) << "  connected" << Debug::flush;
#else
    //    Debug::log(Debug::Spam) << "  connected, fd=" << fd << Debug::flush;
#endif
    return true;
}

bool Socket::disconnect(bool doFlush) {
    if (connected) {
        //        Debug::log(Debug::Debugging) << "Disconnecting socket" << Debug::flush;
        if (doFlush) {
            flush();
        }
        connected = false;
#ifdef _WINDOWS
        closesocket(sock);
#else
        shutdown(sock, SHUT_RDWR);
        close(sock);
#endif
    }
    return true;
}

bool Socket::sendFilecontent(const char* fn, const char* destAddr, int destPort) {
    int fd =  open(fn, O_RDONLY);
    bool status = false;
    if(connect(destAddr,destPort)) {
        off_t len = 0;
        off_t offset = 0;
        int rc = sendfile(fd, sock, offset, &len, NULL,0);
        printf("sendfile: rc=%d len=%d\n",rc,(int)len);
        disconnect(true);
        status = true;
    }
    close(fd);
    return status;
}


bool Socket::sendProblem(const char *data, int len, const char* destAddr, int destPort) {
    bool status = false;
    if(connect(destAddr,destPort)) {
       size_t sizet = write(sock,data,len);
        printf("sendfile:  len=%d\n",(int)sizet);
        disconnect(true);
        status = true;
    }
    return status;
}


bool Socket::emptyWriteBuf() {
    size_t len = writeBufPtr - writeBuf;
    //    Debug::log(Debug::Spam) << "Socket::emptyWriteBuf: len=" << len << Debug::flush;
    ++numWrites;
    totWriteBytes += len;
    if (len > maxWriteBytes) {
        maxWriteBytes = len;
    }
    for (char* ptr = writeBuf; len > 0; ) {
        ssize_t n = send(sock, ptr, len, 0);
        if (n <= 0) {
            if (errno == EPIPE) {
                //                Debug::log(Debug::Warning) << "Other end of socket disconnected" << Debug::flush;
                disconnect(false);
                return false;
            }
            //            Debug::log(Debug::Error) << "Error " << errno << " writing " << len << " bytes to socket"
            //            << Debug::flush;
            return false;
        }
        ptr += n;
        len -= n;
    }
    writeBufPtr = writeBuf;
    return true;
}

bool Socket::fillReadBuf() {
    readBufPtr = readBuf;
    errno = 0;
    ssize_t n = recv(sock, readBuf, _BUF_SIZE, 0);
    if (n <= 0) {
        // EOF results in no error
        if (!errno || errno == EPIPE) {
            //            Debug::log(Debug::Warning) << "Other end of socket disconnected" << Debug::flush;
            disconnect(false);
            return false;
        }
        //        Debug::log(Debug::Error) << "Error " << errno << " reading " << _BUF_SIZE << " bytes from socket"
        //        << Debug::flush;
        return false;
    }
    ++numReads;
    totReadBytes += n;
    if (static_cast<size_t>(n) > maxReadBytes) {
        maxReadBytes = n;
    }
    readValid = readBuf + n;
    return true;
}
